Count of Prime Numbers till N
Difficulty: Easy
Practice Links:
Problem Statement
You are given an integer n. You need to find out the number of prime numbers in the range [1, n] (inclusive). Return the number of prime numbers in the range.
A prime number is a number which has no divisors except 1 and itself.
Mathematical Definition
A positive integer n is prime if:
- n > 1
- n has exactly two positive divisors: 1 and n
The task is to count how many such numbers exist in the range [1, n].
Examples
Example 1:
Input: n = 6
Output: 3
Explanation: Prime numbers in the range [1, 6] are 2, 3, 5.
Example 2:
Input: n = 10
Output: 4
Explanation: Prime numbers in the range [1, 10] are 2, 3, 5, 7.
Example 3:
Input: n = 20
Output: 8
Explanation: Prime numbers in the range [1, 20] are 2, 3, 5, 7, 11, 13, 17, 19.
Example 4:
Input: n = 1
Output: 0
Explanation: 1 is not a prime number, so count is 0.
Example 5:
Input: n = 2
Output: 1
Explanation: Only 2 is prime in the range [1, 2].
Constraints
- 1 ≤ n ≤ 5 × 10^6
- n is a positive integer
Known Prime Counts
Some known prime counts for reference:
- π(10) = 4 (primes: 2, 3, 5, 7)
- π(100) = 25
- π(1000) = 168
- π(10000) = 1229
Where π(n) represents the prime counting function.
Key Concepts
- Prime Counting Function: π(n) = number of primes ≤ n
- Primality Testing: Checking if individual numbers are prime
- Batch Processing: Counting multiple primes efficiently
- Optimization: Using mathematical properties to reduce computation
Approach 1: Brute Force Solution
Algorithm / Intuition
The straightforward approach is to:
- Iterate through all numbers from 1 to n
- Check each number for primality using basic prime checking
- Count all prime numbers found
Core Logic:
- Loop through all numbers from 1 to n
- For each number, use the basic O(k) prime checking algorithm
- Count how many numbers return true for the prime check
- Return the total count
Step-by-Step Algorithm:
- Initialize
count = 0to track prime numbers - Loop from
i = 1toi = n(inclusive) - For each i, call
isPrime(i) - If
isPrime(i)returns true, increment count - Return the final count
DryRun Example (Brute Force):
Input: n = 10
Initial: n = 10, count = 0
i = 1: isPrime(1) = false → count = 0
i = 2: isPrime(2) = true → count = 1
i = 3: isPrime(3) = true → count = 2
i = 4: isPrime(4) = false → count = 2
i = 5: isPrime(5) = true → count = 3
i = 6: isPrime(6) = false → count = 3
i = 7: isPrime(7) = true → count = 4
i = 8: isPrime(8) = false → count = 4
i = 9: isPrime(9) = false → count = 4
i = 10: isPrime(10) = false → count = 4
Final: count = 4
Prime numbers found: 2, 3, 5, 7
Brute Force Code Solutions:
Java
class Solution {
public int primeUptoN(int n) {
// Initialize counter for prime numbers
int count = 0;
// Check each number from 1 to n for primality
for (int i = 1; i <= n; i++) {
// If current number is prime, increment count
if (isPrime(i)) count++;
}
// Return total count of prime numbers
return count;
}
public boolean isPrime(int n) {
// Handle edge case: 1 is not prime by definition
if (n == 1) return false;
// Check all numbers from 2 to n-1 for divisibility
for (int i = 2; i < n; i++) {
// If i divides n evenly, n is not prime
if (n % i == 0) return false;
}
// No divisors found, n is prime
return true;
}
}
JavaScript
class Solution {
primeUptoN(n) {
// Initialize counter for prime numbers
let count = 0;
// Check each number from 1 to n for primality
for (let i = 1; i <= n; i++) {
// If current number is prime, increment count
if (this.isPrime(i)) count++;
}
// Return total count of prime numbers
return count;
}
isPrime(n) {
// Handle edge case: 1 is not prime by definition
if (n == 1) return false;
// Check all numbers from 2 to n-1 for divisibility
for (let i = 2; i < n; i++) {
// If i divides n evenly, n is not prime
if (n % i == 0) return false;
}
// No divisors found, n is prime
return true;
}
}
Python
import math
class Solution:
def primeUptoN(self, n):
# Initialize counter for prime numbers
count = 0
# Check each number from 1 to n for primality using range(1, n+1)
for i in range(1, n + 1):
# If current number is prime, increment count
if self.isPrime(i):
count += 1
# Return total count of prime numbers
return count
def isPrime(self, n):
# Handle edge case: 1 is not prime by definition
if n == 1:
return False
# Check all numbers from 2 to n-1 for divisibility using range(2, n)
for i in range(2, n):
# If i divides n evenly, n is not prime
if n % i == 0:
return False
# No divisors found, n is prime
return True
Brute Force Complexity:
- Time Complexity: O(n²) - for each number up to n, check all numbers up to it
- Space Complexity: O(1) - constant extra space
Approach 2: Optimized Prime Checking
Algorithm / Intuition
We can optimize the prime checking function using the square root optimization:
- Instead of checking all numbers up to n-1, only check up to √n
- This reduces the inner loop complexity from O(k) to O(√k)
- Overall complexity improves from O(n²) to O(n√n)
Core Logic:
- Use the same outer loop to iterate through all numbers
- Optimize the
isPrime()function to only check divisors up to √n - This gives significant performance improvement for larger numbers
Mathematical Reasoning:
For checking if n is prime:
If n has a divisor d > √n, then n/d < √n
So we only need to check divisors up to √n
Step-by-Step Algorithm:
- Initialize
count = 0 - Loop from
i = 1toi = n - For each i, call optimized
isPrime(i)(O(√i) instead of O(i)) - If prime, increment count
- Return count
DryRun Example (Optimized):
Input: n = 10
Initial: n = 10, count = 0
i = 1: isPrime(1) = false (edge case) → count = 0
i = 2: isPrime(2) = true (no divisors to check) → count = 1
i = 3: isPrime(3) = true (no divisors to check) → count = 2
i = 4: isPrime(4) = false (√4=2, 4%2=0) → count = 2
i = 5: isPrime(5) = true (√5≈2.2, check 2: 5%2≠0) → count = 3
i = 6: isPrime(6) = false (√6≈2.4, check 2: 6%2=0) → count = 3
i = 7: isPrime(7) = true (√7≈2.6, check 2: 7%2≠0) → count = 4
i = 8: isPrime(8) = false (√8≈2.8, check 2: 8%2=0) → count = 4
i = 9: isPrime(9) = false (√9=3, check 2,3: 9%3=0) → count = 4
i = 10: isPrime(10) = false (√10≈3.1, check 2: 10%2=0) → count = 4
Final: count = 4
Optimized Code Solutions:
Java
class Solution {
public int primeUptoN(int n) {
// Initialize counter for prime numbers
int count = 0;
// Check each number from 1 to n for primality
for (int i = 1; i <= n; i++) {
// If current number is prime, increment count
if (isPrime(i)) count++;
}
// Return total count of prime numbers
return count;
}
public boolean isPrime(int n) {
// Handle edge case: 1 is not prime by definition
if (n == 1) return false;
// Check divisors only up to √n for efficiency
for (int i = 2; i <= Math.sqrt(n); i++) {
// If i divides n evenly, n is not prime
if (n % i == 0) return false;
}
// No divisors found up to √n, n is prime
return true;
}
}
JavaScript
class Solution {
primeUptoN(n) {
// Initialize counter for prime numbers
let count = 0;
// Check each number from 1 to n for primality
for (let i = 1; i <= n; i++) {
// If current number is prime, increment count
if (this.isPrime(i)) count++;
}
// Return total count of prime numbers
return count;
}
isPrime(n) {
// Handle edge case: 1 is not prime by definition
if (n == 1) return false;
// Check divisors only up to √n for efficiency
for (let i = 2; i <= Math.sqrt(n); i++) {
// If i divides n evenly, n is not prime
if (n % i == 0) return false;
}
// No divisors found up to √n, n is prime
return true;
}
}
Python
import math
class Solution:
def primeUptoN(self, n):
# Initialize counter for prime numbers
count = 0
# Check each number from 1 to n for primality using range(1, n+1)
for i in range(1, n + 1):
# If current number is prime, increment count
if self.isPrime(i):
count += 1
# Return total count of prime numbers
return count
def isPrime(self, n):
# Handle edge case: 1 is not prime by definition
if n == 1:
return False
# Check divisors only up to √n for efficiency using range(2, int(√n)+1)
for i in range(2, int(math.sqrt(n)) + 1):
# If i divides n evenly, n is not prime
if n % i == 0:
return False
# No divisors found up to √n, n is prime
return True
Optimized Complexity:
- Time Complexity: O(n√n) - for each number up to n, check up to √n
- Space Complexity: O(1) - constant extra space
Approach 3: Sieve of Eratosthenes (Most Efficient)
Algorithm / Intuition
The Sieve of Eratosthenes is the most efficient algorithm for finding all primes up to n:
- Create a boolean array to mark numbers as prime/composite
- Start with 2, mark all its multiples as composite
- Move to the next unmarked number, repeat the process
- Count remaining unmarked numbers
Core Logic:
- Initialize boolean array where
isPrime[i] = truemeans i is prime - Mark 0 and 1 as non-prime
- For each prime p, mark all multiples of p as composite
- Count remaining primes
Step-by-Step Algorithm:
- Create boolean array
isPrime[n+1], initialize all as true - Set
isPrime[0] = isPrime[1] = false - For i = 2 to √n:
- If
isPrime[i]is true:- Mark all multiples of i as false
- If
- Count all indices where
isPrime[i]is true
Sieve Code Example:
class Solution {
public int primeUptoN(int n) {
if (n < 2) return 0;
// Create boolean array to mark primes
boolean[] isPrime = new boolean[n + 1];
Arrays.fill(isPrime, true);
// 0 and 1 are not prime
isPrime[0] = isPrime[1] = false;
// Sieve of Eratosthenes
for (int i = 2; i * i <= n; i++) {
if (isPrime[i]) {
// Mark all multiples of i as composite
for (int j = i * i; j <= n; j += i) {
isPrime[j] = false;
}
}
}
// Count primes
int count = 0;
for (int i = 2; i <= n; i++) {
if (isPrime[i]) count++;
}
return count;
}
}
Sieve Complexity:
- Time Complexity: O(n log log n) - much more efficient than previous approaches
- Space Complexity: O(n) - boolean array of size n
Complexity Analysis Summary
| Approach | Time Complexity | Space Complexity | Best For |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Very small n (< 100) |
| Optimized Prime Check | O(n√n) | O(1) | Medium n (< 10^4) |
| Sieve of Eratosthenes | O(n log log n) | O(n) | Large n (≥ 10^4) |
Edge Cases to Consider
- n = 1: No primes in range [1,1], return 0
- n = 2: Only 2 is prime, return 1
- n < 1: Invalid input (based on constraints)
- Large n: Use Sieve for optimal performance
- Memory constraints: Consider space-time trade-offs
Detailed Edge Case Analysis
// Edge cases with step-by-step analysis
Input: 1 → Output: 0 (no primes in [1,1])
Input: 2 → Output: 1 (only 2 is prime)
Input: 3 → Output: 2 (primes: 2, 3)
Input: 4 → Output: 2 (primes: 2, 3)
Input: 5 → Output: 3 (primes: 2, 3, 5)
Input: 10 → Output: 4 (primes: 2, 3, 5, 7)
Test Cases
public void testPrimeCount() {
Solution sol = new Solution();
// Edge cases
assert sol.primeUptoN(1) == 0; // No primes
assert sol.primeUptoN(2) == 1; // Only 2
// Small numbers
assert sol.primeUptoN(3) == 2; // 2, 3
assert sol.primeUptoN(5) == 3; // 2, 3, 5
assert sol.primeUptoN(6) == 3; // 2, 3, 5
assert sol.primeUptoN(10) == 4; // 2, 3, 5, 7
// Larger numbers
assert sol.primeUptoN(20) == 8; // 2,3,5,7,11,13,17,19
assert sol.primeUptoN(100) == 25; // Known result
assert sol.primeUptoN(1000) == 168; // Known result
}
Performance Analysis
Execution Time Comparison:
For n = 10,000:
- Brute Force: ~50,000,000 operations
- Optimized: ~316,000 operations
- Sieve: ~10,000 operations
When to Use Each Approach:
- Brute Force: Only for very small n (< 100) or when space is critical
- Optimized: Good balance for medium n (100 - 10,000)
- Sieve: Best for large n (> 10,000) when space allows
Step-by-Step Visualization
Optimized Approach: n = 6
Check numbers 1 through 6:
i=1: isPrime(1) → false (edge case) → count = 0
i=2: isPrime(2) → check up to √2≈1.4 → no checks needed → true → count = 1
i=3: isPrime(3) → check up to √3≈1.7 → no checks needed → true → count = 2
i=4: isPrime(4) → check up to √4=2 → 4%2=0 → false → count = 2
i=5: isPrime(5) → check up to √5≈2.2 → 5%2≠0 → true → count = 3
i=6: isPrime(6) → check up to √6≈2.4 → 6%2=0 → false → count = 3
Result: 3 primes found (2, 3, 5)
Mathematical Properties
1. Prime Number Theorem
- Asymptotic Density: π(n) ≈ n/ln(n) for large n
- Growth Rate: Primes become less dense as numbers increase
- Distribution: No simple formula for exact count
2. Prime Gaps
- Twin Primes: Primes differing by 2 (3,5), (5,7), (11,13)
- Prime Gaps: Gaps between consecutive primes grow larger
- Bertrand's Postulate: Always a prime between n and 2n for n > 1
3. Computational Limits
- Memory: Sieve needs O(n) space
- Time: Even optimized approaches become slow for very large n
- Approximations: For very large n, use approximation formulas
Common Mistakes to Avoid
- Off-by-one errors: Ensure range is inclusive [1, n]
- Edge case n=1: Remember to return 0
- Efficiency choice: Use appropriate algorithm for input size
- Integer overflow: Be careful with large numbers
- Memory limits: Sieve may not fit in memory for very large n
Optimization Techniques
1. Early Termination in Sieve
for (int i = 2; i * i <= n; i++) {
// Only need to check up to √n
if (isPrime[i]) {
for (int j = i * i; j <= n; j += i) {
isPrime[j] = false;
}
}
}
2. Skip Even Numbers
// After handling 2, only check odd numbers
for (int i = 3; i <= n; i += 2) {
if (isPrime(i)) count++;
}
3. Segmented Sieve
// For very large n, divide into segments
// Process each segment with regular sieve
// Useful when n is too large for single array
Real-World Applications
- Cryptography: RSA key generation requires large prime counts
- Hash Functions: Prime numbers in hash table sizing
- Random Number Generation: Prime-based pseudorandom algorithms
- Mathematical Research: Number theory and prime distribution studies
- Algorithm Analysis: Complexity analysis of prime-related algorithms
- Computer Security: Prime numbers in security protocols
Related Problems
- Nth Prime Number: Find the nth prime number
- Prime Factorization: Find all prime factors of a number
- Goldbach Conjecture: Express even numbers as sum of two primes
- Twin Primes: Find all twin prime pairs up to n
- Prime Gaps: Analyze gaps between consecutive primes
- Segmented Sieve: Handle very large ranges efficiently
Follow-up Questions
- How would you handle very large n (beyond memory limits)?
- Can you modify this to find the sum of all primes up to n?
- How would you implement a segmented sieve for huge ranges?
- What's the space-time trade-off between different approaches?
- How would you parallelize the prime counting algorithm?
Advanced Topics
1. Segmented Sieve
- Purpose: Handle very large n that don't fit in memory
- Method: Divide range into segments, process each segment
- Complexity: Same time, reduced space
2. Prime Number Theorem Applications
- Approximation: π(n) ≈ n/ln(n) for quick estimates
- Error Bounds: More precise approximations exist
- Practical Use: When exact count isn't needed
3. Parallel Prime Counting
- Thread Safety: Divide work among multiple cores
- Load Balancing: Distribute ranges efficiently
- Synchronization: Combine results from different threads
Summary
The prime counting problem demonstrates:
- Algorithm progression from naive to highly optimized approaches
- Space-time trade-offs between different solutions
- Mathematical optimization using number theory insights
- Practical considerations for different input sizes
Key Approaches:
- Brute Force: O(n²) - Simple but inefficient
- Optimized: O(n√n) - Better for medium inputs
- Sieve: O(n log log n) - Best for large inputs
Algorithm Selection:
- Use optimized approach for general cases and space constraints
- Use Sieve of Eratosthenes for large n when space allows
- Consider segmented sieve for extremely large n
This problem serves as an excellent introduction to prime-related algorithms and demonstrates how mathematical insights and algorithmic techniques can dramatically improve performance. The progression from O(n²) to O(n log log n) showcases the power of specialized algorithms for mathematical problems.